#!/usr/bin/perl -w
#
# Script:  rcsplot
# Copyright  (c) 2001, Ian Gilmour <ian@igilmour.freeserve.co.uk>
#
# Description:
#   Perl script to generate a number of RCS/CVS archive activity plots. This
#   script takes a list of RCS files/directories and generates plots of any
#   check-in activity.
#   Plots are generated per RCS file, with summary plots generated per
#   directory. So hierarchies of RCS directories are also be supported.
#
#   The script invokes 'rlog', 'co', & 'wc' to produce the archive information.
#   The paths defined below may need to be configured for your environment.
#
#   A hierarchy of html files (with links to subdirectory html pages) are
#   generated to allow the data to be viewed easily (you'll need a frame
#   capable browser).
#
#   rcsplot should be run on the cvs archive itself and not individual sandboxes.
#
# Usage:
#     rcsplot [rcs_file_names] [cvs_directory_names] [rlog_parameters] [options]
#
#   where [options] can be any combination of,
#
#   -output=dir_path     places the html and png plots in the specified output
#                        directory ('dir_path'). A hierachy of directories
#                        will be created below this directory, as required.
#                        (default: 'rcsplots' off current directory.)
#
#   -columns=n           restricts (where possible) the png plots to a maximum
#                        of 'n' columns. (default: 50)
#
#   -period=n            restricts the png plots to produce a single column
#                        for every 'n' days of archive activity.
#                        (default: 7 [more if 'columns' exceed it's maximum])
#
#   -modified            generate information only for files/directories that 
#                        have been modified. Useful for identifying differences 
#                        between 2 releases of software.
#
#   -datelogonly         only produce date log html file.
#
#   -nochangelog         inhibit production of individual change log files.
#
# The following are not supported yet...
#
#   -noplots             inhibit production of graphical plots.
# 
#   -levels=n            generate plots only for n levels of subdirectories
#                        (it still summarises data below this though).
#
#   Parameters can be specified in any order.
#   Parameters that are not files, directories, or any of the above are assumed 
#   to be 'rlog' parameters. These may need to be escaped using quotes (as below).
#
#   'rlog' parameters can typically be used to reduce the plots to cover a
#   date range, or 2 specific versions, or check-ins by specific user(s).
#   Any valid rlog parameters can be specified.
#
#   e.g.
#     rcsplot xxx/RCS/*,v yyy/RCS -wigilmour "-d2000/10/10<2001/01/01" -output=rcsplots -columns=40
#
# Apologies:
#   I am no Perl expert so apologies to those of you who are. I'm sure this
#   code can be written better. But it does the job I required of it.
#   If you find any errors, or can improve on it - please e-mail me the
#   alterations to the address below.
#
# Dependencies:
#   This script was developed to run under Linux using,
#      Perl 5.6.0
#      GD::Chart (v1.33)
#      rlog (from rcs 5.7)
#      co (from rcs 5.7)
#      wc (GNU textutils 2.0)
#   It may work with older, or newer versions of the same packages.
#
# Contact:
#   email: ian@igilmour.freeserve.co.uk
#   www:   http://www.igilmour.freeserve.co.uk/rcsplot.htm
#
# License:
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   To receive a copy of the GNU General Public License write to the Free Software
#   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
# ChangeLog:
#  0.74 28-Sept-2001
#      changed back to rcsplot.
#      (found someone had already registered cvsplot name at sourceforge etc.)
#      Registered rcsplot first myself just in case!
#  0.73 1-Sept-2001
#      moved over to use GD::Chart module. Renamed 'rcsplot'.
#  0.72 27-April-2001
#      added switches to generate date log only.
#      tidied up some path stuff.
#  0.71 18-April-2001
#      Original release of 'rcsplot'.
#
#
use GD::Graph::bars;
use Time::Local;
use File::Find;
use File::Path;
use File::Spec;
use Cwd;

no strict "refs";

# define some constants
my $version = "v0.74";
my $secs_per_day = (24 * 60 * 60);
my $secs_per_week = (7 * $secs_per_day);

###############################################################################
# Configuration Parameters - these should be all you need to 
#                            tweak to get it working.

# define html background colours
my $bgcolor1 = "#ffffcc";
my $bgcolor2 = "#ccffff";
my $bgcolor3 = "#ffffff";

my $max_num_plot_columns = 50;
my $min_secs_per_period = $secs_per_week;

# alter paths to the following, if necessary
my $rlog = "/usr/bin/rlog";
my $co = "/usr/bin/co";
my $wc = "/usr/bin/wc";

#default output directory
my $output_dir = "rcsplots";
#
# end of configuration parameters
###############################################################################

my @rlog_params = ();
my @cmdline_params = ();
my @f_list = ();
my @d_list = ();
my @src_list = ();
my %date_log = ();
my $modified_only = 0;

$level = 0;
$levels_reqd = 0;
$date_log_reqd = 1;
$change_log_reqd = 1;
$tladd = "total_lines_add";
$tlsub = "total_lines_sub";


###############################################################################
# process input params.
foreach $_ (@ARGV) {

  $cmdline_params[++$#cmdline_params] = $_;

  if (-l $_) {
    # skip symbolic links to files/directories
  } elsif (-d $_) {
    $d_list[++$#d_list] = File::Spec->rel2abs($_);
  } elsif ((-f $_)) {
    if ($_ =~ /.*,v$/) {
      $f_list[++$#f_list] = File::Spec->rel2abs($_);
    } else {
      print "Ignoring non-RCS file: '$_'\n"
    }
  } elsif ($_ =~ /^-modified$/) {
    $modified_only = 1;
  } elsif ($_ =~ /^-output=.*/) {
    s/^-output=//;
    s/\/$//;
    $output_dir =  File::Spec->rel2abs($_);
  } elsif ($_ =~ /^-columns=.*/) {
    s/^-columns=//;
    $max_num_plot_columns = $_;
  } elsif ($_ =~ /^-datelogonly$/) {
    $date_log_reqd = 0;
    $change_log_reqd = 0;
  } elsif ($_ =~ /^-nochangelog$/) {
    $change_log_reqd = 0;
  } elsif ($_ =~ /^-levels=.*/) {
    s/^-levels=//;
    $levels_reqd = $_;
  } elsif ($_ =~ /^-period=.*/) {
    s/^-period=//;
    $min_secs_per_period = $_ * $secs_per_day;
  }
  else {
    $rlog_params[++$#rlog_params] = $_;
  }
}

@src_list = (@d_list, @f_list);

generate_dir_archive_data ($output_dir, \@src_list, 'Top level Archive', '');

generate_date_ordered_log (File::Spec->catfile ($output_dir, 
						"archive_date_log.html"));

exit;

###############################################################################
# process  file/directory list (traverse down tree recursively)
#
sub generate_dir_archive_data {
  # use strict;
  # no strict "refs";
  my $output_root = $_[0];
  my $src_list = $_[1];
  my $summary_title = $_[2];
  my $current_dir = $_[3];
  my $file = "";
  my @file_list = ();
  my $dir = "";
  my @dir_list = ();
  my @new_dir_list = ();
  my @param_list = ();
  my $y_max = 0;
  my @dir_file_list = ();
  my @data = ();
  my @full_data = ();
  my @summed_data = ();
  my @summary_data = ();
  my @param_data = ();
  my $i = 0;
  my $j = 0;
  my $k = 0;
  my $min_date = "";
  my $max_date = "";
  my %unique_dates = ();
  my %summed_unique_dates = ();
  my @rev_dates = ();
  my $period = 0;
  my $title_str = "";
  my @dir_modified = ();
  my @file_modified = ();

  $level++;

  # clearout (possible) old author totals
  %{$tladd.$level} = ();
  %{$tlsub.$level} = ();
  
  # ensure target directory exists
  if (($date_log_reqd) || (!$date_log_reqd && ($level == 1))) {
    if (! (-d File::Spec->catfile ($output_root, $current_dir))) {
      mkpath (File::Spec->catfile ($output_root, $current_dir), 0, 0755) 
	or die "Failed to create target directory '". File::Spec->catfile ($output_root, $current_dir). "'";
    }
  }

  # identify top-level files and directories in source directory
  $i = 0;
  while (defined ($src_list->[$i])) {
    $_ = $src_list->[$i];
    if (-l File::Spec->catfile ($current_dir, $_)) {
      # skip symbolic links to files/directories
    }
    elsif (-d File::Spec->catfile ($current_dir, $_)) {
      $dir_list[++$#dir_list] = $_;
    } elsif (-f File::Spec->catfile ($current_dir, $_)) {
      if ($_ =~ /.*,v$/) {
	$file_list[++$#file_list] = $_;
      }
      else {
	print "ignoring non-RCS file: '".File::Spec->catfile ($current_dir, $_). "'\n";
      }
    } else {
      print "ignoring '$_' [".File::Spec->catfile ($current_dir, $_)."]\n";
    }
    $i++;
  }

  # now generate rlog info for files/directories 
  $i = 0;
  foreach $dir (@dir_list) {
    print "processing '". File::Spec->canonpath (File::Spec->catfile ($current_dir, $dir)). "'...\n";

    # expand directory contents ignoring "." and ".."
    opendir THISDIR,  File::Spec->canonpath (File::Spec->catfile ($current_dir, $dir)) 
      or die "failed to open '". File::Spec->catfile ($current_dir, $dir)."'\n";
    @new_dir_list  = sort (grep !/^\.\.?$/, readdir THISDIR);
    closedir THISDIR;

    if (length($dir) > 53) {
      $title_str =  "'". substr(File::Spec->canonpath(File::Spec->catfile ($current_dir, $dir)), 10, 10). 
	"..." . substr(File::Spec->canonpath(File::Spec->catfile ($current_dir, $dir)),-40, 40) . "'";
    }  else {
      $title_str = "'". File::Spec->canonpath(File::Spec->catfile ($current_dir, $dir)) . "'";
    };
    push @{$data[++$#data]}, generate_dir_archive_data($output_root, \@new_dir_list, 
						       $title_str, File::Spec->catfile ($current_dir, $dir));
    if (defined ($data[$#data]->[0][0])) {
      $dir_modified[$i] = 1;
    } else {
      $dir_modified[$i] = 0;
    }
    $i++;
  }
  
  $i = 0;
  foreach $file (@file_list) {
    if (!$change_log_reqd) {
      $output_file = File::Spec->catfile (File::Spec->tmpdir() , "rcsplot" . getppid() . ".tmp");
    }else {
      $output_file = File::Spec->catfile ($output_root, $current_dir, $file . ".txt");
    }
    push @{$data[++$#data]}, generate_rcs_data(File::Spec->catfile ($current_dir, $file), $output_file);
    if (defined ($data[$#data]->[0][0])) {
      $file_modified[$i] = 1;
    } else {
      $file_modified[$i] = 0;
    }
    $i++;
  }
  if (!$change_log_reqd) {
    unlink (File::Spec->catfile (File::Spec->tmpdir() , "rcsplot" . getppid() . ".tmp"));
  }

  ($min_date, $max_date, %unique_dates) = find_unique_dates(\@data);
  
  $period = calc_sample_period($min_date, $max_date);
  
  # sum data per period 
  @summed_data = sum_all_data_per_period (\@data, $max_date, $min_date, $period);
  
  $y_max = find_y_max (\@summed_data);
  
  ($min_date, $max_date, %summed_unique_dates) = find_unique_dates(\@summed_data);
  
  @rev_dates = reverse (sort (keys %summed_unique_dates));
  @full_data = add_all_missing_dates (\@summed_data, \@rev_dates);
  
  # generate archive summary 
  @summary_data = generate_summary_data (\@full_data);
  
  if ((!$modified_only) || ($level == 1) || (defined $summary_data[0][0])) {

    # generate data of activity per file/directory
    @param_list = (@dir_list, @file_list);
    push @{$param_data[++$#param_data]}, generate_param_data (\@data, \@param_list);
  
    ###############################################################################
    #accumulate author data to next level up
    if ($level > 1) {
      my $levelm1 = $level-1;
      foreach $author ((sort (keys (%{$tladd.$level})))) {
	${$tladd.$levelm1}{$author} += ${$tladd.$level}{$author};
        ${$tlsub.$levelm1}{$author} += ${$tlsub.$level}{$author};
      };
    };


    if ($date_log_reqd) {
      ###############################################################################
      # generate PNG plots
      $i = 0;
      $j = 0;
      foreach $file (@dir_list) {
	if ((!$modified_only) || $dir_modified[$i]) {
	  generate_data_bar_plot(File::Spec->canonpath (File::Spec->catfile ($current_dir, $file)), 
				 File::Spec->catfile ($output_root, $current_dir, 'file') . ($j*2), 
				 $full_data[$j], $y_max, 0, "(fixed)", 200);
	  generate_data_bar_plot(File::Spec->canonpath (File::Spec->catfile ($current_dir, $file)), 
				 File::Spec->catfile ($output_root, $current_dir, 'file') . ($j*2+1), 
				 $full_data[$j], -1, 0, "(scaled)", 200);
	}
	$j++;
	$i++;
      };
      
      $i = 0;
      foreach $file (@file_list) {
	if ((!$modified_only) || $file_modified[$i]) {
	  generate_data_bar_plot(File::Spec->canonpath (File::Spec->catfile ($current_dir, $file)), 
				 File::Spec->catfile ($output_root, $current_dir, 'file') . ($j*2), 
				 $full_data[$j], $y_max, 0, "(fixed)", 200);
	  generate_data_bar_plot(File::Spec->canonpath (File::Spec->catfile ($current_dir, $file)), 
				 File::Spec->catfile ($output_root, $current_dir, 'file') . ($j*2+1), 
				 $full_data[$j], -1, 0, "(scaled)", 200);
	}
	$j++;
	$i++;
      };
      
      $y_max = find_single_y_max (\@summary_data);
      
      generate_data_bar_plot("$summary_title Summary", 
			     File::Spec->catfile ($output_root, $current_dir, "archive_summary"), 
			     \@summary_data, $y_max, 1, "", 200);
      
      $y_max = find_y_max (\@param_data);
      generate_data_bar_plot("$summary_title Activity", 
			     File::Spec->catfile ($output_root, $current_dir, "archive_param_data"), 
			     $param_data[0], $y_max, 1, "", 300);
      
      # create author bar chart
      generate_author_bar_plot(File::Spec->catfile ($output_root, $current_dir, "archive_authors_plot"));
      
      # summary files
      generate_html_frame_file(File::Spec->catfile ($output_root, $current_dir, "archive_summary_fixed.html"), "fixed");
      generate_html_frame_file(File::Spec->catfile ($output_root, $current_dir, "archive_summary_scaled.html"), "scaled");
      
      generate_html_summary_file (File::Spec->catfile ($output_root, $current_dir, "archive_hdr.html"));
      
      generate_html_fixed_data_file (File::Spec->catfile ($output_root, $current_dir, "archive_body_fixed.html"), 
				     \@dir_list, \@file_list, $current_dir, $level, $output_root, \@dir_modified, \@file_modified);
      
      generate_html_scaled_data_file (File::Spec->catfile ($output_root, $current_dir, "archive_body_scaled.html"), 
				      \@dir_list, \@file_list, $current_dir, $level, $output_root, \@dir_modified, \@file_modified);
    }
  }

  $level--;
  
  return @summary_data;

  ###############################################################################
  # generate html top frame file
  sub generate_html_frame_file {
    my $file = $_[0];
    my $body_type = $_[1];

    print "  generating '$file'\n";

    open HTMLFILE,">$file" 
      or die "Can't create '$file' file";

    print_html_header (\*HTMLFILE, "Archive Statistics");

    print HTMLFILE <<EOS;
  <frameset rows="250,*">
     <frame scrolling=yes src="archive_hdr.html">
EOS

    if ($body_type eq 'fixed') {
      print HTMLFILE <<EOS;
     <frame scrolling=yes src="archive_body_fixed.html">
EOS
    } else {
      print HTMLFILE <<EOS;
     <frame scrolling=yes src="archive_body_scaled.html">
EOS
    }
    print HTMLFILE <<EOS;
     <noframes>
       Sorry - you need a browser that supports frames.
     </noframes>
  </frameset>
</html>
EOS

    close HTMLFILE;
  }

  ###############################################################################
  # generate html summary file
  sub generate_html_summary_file {
    my $file = $_[0];

    print "  generating '$file'\n";

    open HTMLFILE,">$file" 
      or die "Can't create '$file' file";

    print_html_header (\*HTMLFILE,"Archive Statistics - header");

    print HTMLFILE "  <body bgcolor=\"$bgcolor3\">\n";
    print HTMLFILE "    <table bgcolor=\"$bgcolor3\" width=\"100\%\">\n";
    print HTMLFILE <<EOS;
      <tr>
        <td align="center"><img src="archive_summary.png" alt="archive summary plot"></td>
      </tr>
    </table>
  </body>
</html>
EOS

    close HTMLFILE;
  }

  ###############################################################################
  # print main html header
  sub print_html_header {
    my $file = $_[0];
    my $title = $_[1];

    print $file <<EOS;
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
    <meta name="GENERATOR" content="">
EOS
    print $file "    <title>$title</title>\n  </head>\n";
    return;
  }

  ###############################################################################
  # print body header
  sub print_body_header {
    my $file = $_[0];
    my $fixed_body_type = $_[1];
    my $level = $_[2];
    my $output_root = $_[3];
    my $current_dir = $_[4];

    print $file "<body bgcolor=\"$bgcolor3\">\n";

    print $file "<font face=\"Verdana, Helvetica, Arial\">\n";

    print $file "<table bgcolor=\"$bgcolor3\" width=\"100\%\">\n<tr>\n";

    if ($fixed_body_type) {
      print $file "<td bgcolor=\"$bgcolor1\" align=\"center\"><b>fixed</b> <a href=\"archive_body_scaled.html\"><b>scaled</b></a>\n";

      if ($level > 2) {
	print $file " <a href=\"../archive_summary_fixed.html\" target=\"_top\"><b>parent</b></a>";
      } elsif ($level == 2) {
	print $file " <a href=\"". 
	  File::Spec->abs2rel("$output_root/archive_summary_fixed.html", $current_dir) . 
	    "\" target=\"_top\"><b>parent</b></a>";
      }
    } else {
      print $file "<td bgcolor=\"$bgcolor1\" align=\"center\"><a href=\"archive_body_fixed.html\"><b>fixed</b></a> <b>scaled</b>\n";

      if ($level > 2) {
	print $file " <a href=\"../archive_summary_scaled.html\" target=\"_top\"><b>parent</b></a>";
      } elsif ($level == 2) {
	print $file " <a href=\"". 
	  File::Spec->abs2rel("$output_root/archive_summary_scaled.html", $current_dir) . 
	    "\" target=\"_top\"><b>parent</b></a>";
      }
    }

    print $file " <a href=\"" .  
      File::Spec->abs2rel("$output_root/archive_date_log.html",  $current_dir) . 
	"\" target=\"date_log\"><b>date log</b></a>\n";

    print $file <<EOS;
          <br>
          '<b>fixed</b>' plots are done with all plots having the same y-max value to ease comparison between plots.
          <br>
          '<b>scaled</b>' plots are scaled per plot to emphasis modifications.
          <br>
        </td>
      </tr>
      <tr>
        <td align="center"><img src="archive_authors_plot.png" alt="per author plot"></td>
      </tr>
      <tr>
        <td align="center"><img src="archive_param_data.png" alt="per file/directory summary plot"></td>
      </tr>
    </table>
    <br>
EOS

  print $file "<table bgcolor=\"$bgcolor3\" width=\"100\%\">\n";
  }

  ###############################################################################
  # print html footer
  sub print_html_footer {
    my $file = $_[0];
    my ($sec, $min, $hour, $mday, $mon, $year) = localtime;

    $year += 1900;
    $mon += 1;

    print $file <<EOS;
  <hr>
  <b>
  <font face="Arial Narrow">
EOS

    print $file "Generated: $year/$mon/$mday, using <a href=\"http://www.igilmour.freeserve.co.uk/rcsplot.htm\">rcsplot</a> $version.\n";
    print $file "<br>Command line parameters: '@cmdline_params'\n";
    print $file <<EOS;
  </font>
  </b>
</html>
EOS
  } 

  ###############################################################################
  # print html fixed data file 
  sub generate_html_fixed_data_file {
    no strict 'vars';
    
    my $out_file = $_[0];
    my $dir_list = $_[1];
    my $file_list = $_[2];
    my $current_dir = $_[3];
    my $level = $_[4];
    my $output_root = $_[5];
    my $dir_modified = $_[6];
    my $file_modified = $_[7];
    my $dir = "";
    my $file = ""; 
    my $k = 0;
    my $j;

    print "  generating '$out_file'\n";

    open HTMLFILE,">$out_file" 
      or die "Can't create '$out_file' file";
    
    print_html_header(\*HTMLFILE, "Fixed Archive Activity");
    print_body_header(\*HTMLFILE, 1, $level, $output_root, 
		      File::Spec->catdir ($output_root, $current_dir));

    $j = 0;
    while (defined ($dir_list->[$j])) {
      if ((!$modified_only) || ($dir_modified->[$j])) {
	$dir = $dir_list->[$j];
	$dir = '.'.$dir if ($level==1);
	print HTMLFILE "	    <tr><td align=\"center\"><a href=\"". 
	  File::Spec->catfile ($dir, "archive_summary_fixed.html"). 
	    "\" target=\"_top\"><img src=\"file$k.png\" alt=\"". 
	      File::Spec->canonpath (File::Spec->catfile ($current_dir, $dir)) . 
	      " plot\"></a><\/td></tr>\n";
      }
      $k += 2;
      $j++;
    }
    
    $j = 0;
    while (defined ($file_list->[$j])) {
      if ((!$modified_only) || ($file_modified->[$j])) {
	$file = $file_list->[$j];
	print HTMLFILE "	    <tr><td align=\"center\"><a href=\"". $file . 
	  ".txt\" target=\"rcsinfo\"><img src=\"file$k.png\" alt=\"" . 
	    File::Spec->canonpath ("$current_dir/$file") . " plot\"></a><\/td></tr>\n";
      }
      $k += 2;
      $j++;
    }

    print HTMLFILE <<EOS;
  </table>
  </font>
  <br>
EOS
    print_html_footer (\*HTMLFILE);

    close(HTMLFILE);
  }
  
  ###############################################################################
  # print html scaled data file 
  sub generate_html_scaled_data_file {
    no strict 'vars';
    
    my $out_file = $_[0];
    my $dir_list = $_[1];
    my $file_list = $_[2];
    my $current_dir = $_[3];
    my $level = $_[4];
    my $output_root = $_[5];
    my $dir_modified = $_[6];
    my $file_modified = $_[7];
    my $dir = "";
    my $file = ""; 
    my $k = 1;
    my $j = 0;

    print "  generating '$out_file'\n";

    open HTMLFILE,">$out_file"
      or die "Can't create '$out_file' file";
    
    print_html_header(\*HTMLFILE, "Scaled Archive Activity");
    print_body_header(\*HTMLFILE, 0, $level, $output_root, 
		      File::Spec->catdir ($output_root, $current_dir));
    
    while (defined ($dir_list->[$j])) {
      if ((!$modified_only) || ($dir_modified->[$j])) {
	$dir = $dir_list->[$j];
	$dir = '.'.$dir if ($level==1);
	print HTMLFILE "	    <tr><td align=\"center\"><a href=\"". 
	  File::Spec->canonpath (File::Spec->catfile ($dir, "archive_summary_scaled.html")).
	    " \" target=\"_top\"><img src=\"file$k.png\" alt=\"" . 
	      File::Spec->catfile ($current_dir, $dir) . 
		" plot\"></a><\/td></tr>\n";
      }
      $k += 2;
      $j++;
    }
    
    $j = 0;
    while (defined ($file_list->[$j])) {
      if ((!$modified_only) || ($file_modified->[$j])) {
	$file = $file_list->[$j];
	print HTMLFILE "	    <tr><td align=\"center\"><a href=\"". $file . 
	  ".txt\" target=\"rcsinfo\"><img src=\"file$k.png\" alt=\"".
	    File::Spec->canonpath ("$current_dir/$file"). " plot \"></a><\/td></tr>\n";
      }
      $k += 2;
      $j++;
    }
    
    print HTMLFILE <<EOS;
  </table>
  </font>
  <br>
EOS
    print_html_footer (\*HTMLFILE);
    
    close(HTMLFILE);
  }
  
  ############################################################################### 
  # print_data - for debug
  #
  sub print_data {
    my @dataset = @_;
    for my $i (0..$#dataset) {
      for my $j (0..$#{$dataset[$i]}) {
	for my $k (0..$#{$dataset[$i][$j]}) {
	  print "--- $i,$j,$k $dataset[$i][$j][$k]\n";
	}
      }
    }
  }
  
  ############################################################################### 
  # generate single bar chart plot
  #
  sub generate_data_bar_plot {
    my $title = $_[0];
    my $file = $_[1];
    my $dataref = $_[2];
    my $y_max = $_[3];
    my $legend = $_[4];
    my $text = $_[5];
    my $y_size = $_[6];
    my $label = "";
    my $num_entries = 0;
    my $width = 0;
    
    print "  generating '$file.png'\n";
    # dummy data for missing plots
    my @data = ( 
		["dummy1"],
		[     0  ],
		[     0  ],
	       );
    
    # count number of table entries
    while (defined ($dataref->[0][$num_entries])) {
      $num_entries++;
    };
    
    if ((! defined ($dataref)) 
	|| (! defined ($dataref->[0]))
	|| (! defined ($dataref->[1]))
	|| (! defined ($dataref->[2]))
	|| ($num_entries < 1)) {
      $dataref = \@data;
      $y_max = -1;
    }
    
    # PNGGraph cannot handle scaling if $y_max == 0, so...
    if ($y_max == 0) {
      $y_max = 1;
    }

    $width = $num_entries * 10 + 50;
    
    # set minimum plot width
    $width = 500 if $width < 500;
    
    my $my_graph = new GD::Graph::bars($width, $y_size);
    
    # the following seems to give a reasonable title string
    # change the image size and you may need to play with it.
    if (length($title) > $width/10) {
      $label = substr ($title, 0, int($width/40)-3) . "..." . 
	substr ($title, int(-$width * 3)/40, int($width * 3)/40) . $text;
    }  else {
      $label = $title . $text;
    };
    
    if ($y_max != -1) {
      $my_graph->set(
		     x_labels_vertical => 1,
		     y_label => 'lines',
		     title => $label,
		     y_max_value => $y_max,
		     long_ticks => 1,
		     overwrite => 2,
		     x_label_position => 1/2,
		     legend_placement => 'LB',
		     y_min_value => 0,
		    );
    } else {
      $my_graph->set(
		     x_labels_vertical => 1,
		     y_label => 'lines',
		     title => $label,
		     long_ticks => 1,
		     overwrite => 2,
		     x_label_position => 1/2,
		     legend_placement => 'LB',
		     y_min_value => 0,
		    );
    };
    
    if ($legend == 1) {
      $my_graph->set_legend( 'lines deleted', 'lines added' );
    };
    
    my $gd = $my_graph->plot( $dataref );
    open (IMG, ">$file.png") or die $!;
    binmode IMG;
    print IMG $gd->png;
    close IMG;
  };
  
  
  sub date2str {
    if (defined $_[0]) {
      my ($sec, $min, $hour, $mday, $mon, $year) = localtime ($_[0]);
      $year += 1900;
      $mon++;
      $mon = '0' . $mon if ($mon <= 9);
      $mday = '0' . $mday if ($mday <=9);
      return $year . "/" . $mon . "/" . $mday;
    } else {
      return undef;
    }
  }
    
  
  sub str2date {
    my ($year, $month, $day) = (0,0,0);
    if (defined $_[0]) {
      ($year, $month, $day) = split /\//, $_[0];
      $month--;
      $year -= 1900;
      return timelocal (0,0,0, $day, $month, $year);
    } else {
      return undef;
    }
  }
  
  ###############################################################################
  # add missing dates to single data set - 0,0 data added
  #
  sub sum_data_per_period {
    my $data_in = $_[0];
    my $period = $_[3];
    my $current_max_date = str2date($_[1]);
    my $min_date = $_[2];
    my @data_out = ();
    my $sum_sub = 0;
    my $sum_add = 0;
    my $j = 0;
    
    if ((defined $data_in->[0][$j]) 
	&& (defined $current_max_date)) {
      while (defined $data_in->[0][$j]) {
	if (($current_max_date - $period) >= str2date($data_in->[0][$j])) {
	  # we've accumulated all the data for the period 
	  push @{$data_out[0]}, date2str($current_max_date);
	  push @{$data_out[1]}, $sum_sub;
	  push @{$data_out[2]}, $sum_add;
	  $sum_sub = 0;
	  $sum_add = 0;
	  $current_max_date -= $period;
	} else {
	  # accumulate data for current period
	  $sum_sub += $data_in->[1][$j];
	  $sum_add += $data_in->[2][$j];
	  $j++;
	}
      };
      
      push @{$data_out[0]}, date2str($current_max_date);
      push @{$data_out[1]}, $sum_sub;
      push @{$data_out[2]}, $sum_add;
      $current_max_date -= $period;
      
      # rcs data complete - zero remaining data
      while ($current_max_date > str2date($min_date)) {
	push @{$data_out[0]}, date2str($current_max_date);
	push @{$data_out[1]}, 0;
	push @{$data_out[2]}, 0;
	$current_max_date -= $period;
      };
    }
    
    return @data_out;
  }
  
  ###############################################################################
  # sum data over specified interval period
  #
  sub sum_all_data_per_period {
    my $data_in = $_[0];
    my $max_date = $_[1];
    my $min_date = $_[2];
    my $period = $_[3];
    my @data_out = ();
    my $i = 0;
    
    while (defined $data_in->[$i]) {
      push @{$data_out[++$#data_out]}, sum_data_per_period ($data_in->[$i], $max_date, $min_date, $period);
      $i++;
    }
    
    return @data_out;
  }
  
  
  ###############################################################################
  # add missing dates to single data set - 0 data added
  #
  sub add_missing_dates {
    my $data_in = $_[0];
    my $dates = $_[1];
    my @data_out = ();
    my $j = 0;
    my $k = 0;
    while ((defined $data_in->[0][$j]) && (defined $dates->[$k])) {
      if ($dates->[$k] gt $data_in->[0][$j]) {
	push @{$data_out[0]}, $dates->[$k++];
	push @{$data_out[1]}, 0;
	push @{$data_out[2]}, 0;
      } else {
	push @{$data_out[0]}, $data_in->[0][$j];
	push @{$data_out[1]}, $data_in->[1][$j];
	push @{$data_out[2]}, $data_in->[2][$j++];
	$k++;
      }
    };
    
    while (defined $dates->[$k]) {
      push @{$data_out[0]}, $dates->[$k++];
      push @{$data_out[1]}, 0;
      push @{$data_out[2]}, 0;
    };
    
    return @data_out;
  }
  
  ###############################################################################
  # add missing dates to all data sets - 0 sub and add data added
  #
  sub add_all_missing_dates {
    my $data_in = $_[0];
    my $dates = $_[1];
    my @data_out = ();
    my $i = 0;
    while (defined $data_in->[$i]) {
      push @{$data_out[++$#data_out]}, add_missing_dates ($data_in->[$i], $dates);
      $i++;
    }
    
    #print_data (@data_out);
    return @data_out;
  }
  
  
  ###############################################################################
  # generate plot of activity per author
  #
  sub generate_author_bar_plot {
    no strict 'vars';
    
    my $file = $_[0];
    my @data = ();
    my $author = "";
    
    foreach $author ((sort (keys (%{$tlsub.$level})))) {
      push @{$data[0]}, $author;
      push @{$data[1]}, ${$tlsub.$level}{$author};
      push @{$data[2]}, ${$tladd.$level}{$author};
    };
    
    # include dummy data entry if no data present
    if ($#data < 0) {
      push @{$data[0]}, "nobody";
      push @{$data[1]}, 0;
      push @{$data[2]}, 0;
    };
    
    generate_data_bar_plot ("Author activity", $file, \@data, -1, 0, "", 200);
  }
  

  ###############################################################################
  # generate single archive data set from a single file
  #
  sub generate_rcs_data {
    my $file = $_[0];
    my $outfile = $_[1];
    my @d = ();
    my $date;
    my $last_date = "";
    my $lines_add = 0;
    my $lines_sub = 0;
    my $lines_add_total = 0;
    my $lines_sub_total = 0;
    my $author = "";
    my @args = ();
    my @param = ();
    my $param = ();
    my $revision = "";
    
    print "  generating '$outfile'\n";

    $args[++$#args] = "$rlog";
    foreach $param ( @rlog_params ) {
      $args[++$#args] = $param;
    };
    $args[++$#args] = $file;
    
    # create rlog data file
    open SAVEOUT, ">&STDOUT";
    open STDOUT,">$outfile" or die "Can't redirect stdout";
    if (!(system (@args) == 0)) {
      print "invalid archive file:'$file', or parameter '@rlog_params'\n";
      close(STDOUT);
      open STDOUT,">&SAVEOUT";
      return;
    };
    
    close(STDOUT);
    open STDOUT,">&SAVEOUT";
    
    # process rlog data file
    if (!(open RLOGFILE,"<$outfile" )) {
      print "Can't open rlog file: '$outfile'\n";
      return;
    };
    
    while (<RLOGFILE>) {
      if (/^revision/) {
	(@param) = split /\s+/;
	$revision = $param[1];
      };
      
      if (/^date:/) {
	s/\;//g;
	s/\+//g;
	chop;
	
	(@param) = split /\s+/;
	$date = $param[1];
	$author = $param[4];
	if (defined $param[8]) {
	  $lines_add = $param[8];
	} 
	else {
	  # initial check-in - get initial number of lines using 'co'
	  $lines_add = `$co -q -p -r$revision $file | $wc -l`;
	  #print "$lines_add\n";
	  #$lines_add = 0;
	};
	if (defined $param[9]) {
	  $lines_sub = -$param[9];
	}
	else {
	  $lines_sub = 0;
	};
	
	# treat 1st entry special
	if ($last_date eq "") {
	  $last_date = $date;
	}
	

	if ($date ne $last_date) {
	  push @{$d[0]}, $last_date;
	  push @{$d[1]}, $lines_sub_total;
	  push @{$d[2]}, $lines_add_total;
	  
	  $lines_add_total = $lines_add;
	  $lines_sub_total = $lines_sub;
	  ${$tladd.$level}{$author} += $lines_add;
          ${$tlsub.$level}{$author} += $lines_sub;
	  
	} else {
	  # multiple entry for same date - sum data
	  $lines_add_total += $lines_add;
	  $lines_sub_total += $lines_sub;
	  
	  ${$tladd.$level}{$author} += $lines_add;
          ${$tlsub.$level}{$author} += $lines_sub;
	};
	$last_date = $date;

	$date_log {$date} .= "$file\nrevision: $revision\n$_\n";

	while (<RLOGFILE>) {
	  if (/^-*-$/ || /^=*=/) {
	    last;
	  } else {
	    $date_log {$date} .= "$_\n";
	  }
	}
      }
    };
    
    if (defined $date) {
      push @{$d[0]}, $date;
      push @{$d[1]}, $lines_sub_total;
      push @{$d[2]}, $lines_add_total;
    }
    
    if ($author ne "") {
      ${$tladd.$level}{$author} += $lines_add;
      ${$tlsub.$level}{$author} += -$lines_sub;
    }
    
    close(RLOGFILE); 
    
    # delete empty data file 
    if (($modified_only) && (!defined $d[0])) {
      print "  deleting empty file: '$outfile'\n";
      unlink ($outfile);
    }

    return @d;
  };
  
  
  ###############################################################################
  # find max y axis value from 1 dataset
  #
  sub find_single_y_max {
    my $dataref = $_[0];
    my $j = 0;
    my $y_max = 0;
    my $y = 0;
    
    while (defined $dataref->[1][$j]) {
      $y = $dataref->[1][$j] + $dataref->[2][$j];
      $y_max = $y if ($y > $y_max);
      $j++;
    }
    
    #ensure y_max is divisible by 5 to give integer only y-axis values on plots
    $y_max = int(($y_max + 4) / 5) * 5;
    
    return $y_max;
  }
  
  
  
  ###############################################################################
  # find max y axis value from a number of datasets
  #
  sub find_y_max {
    my $dataref = $_[0];
    my $i = 0;
    my $y_max = 0;
    my $y = 0;
    
    while (defined $dataref->[$i]) {
      $y = find_single_y_max ($dataref->[$i]);
      $y_max = $y if ($y > $y_max);
      $i++;
    }
    
    return $y_max;
  }
  
  ###############################################################################
  # generate a single data set by summing a number of data sets.
  #
  sub generate_summary_data
    {
      my $dataref = $_[0];
      my $i = 0;
      my @data = ();
      my %sub = ();
      my %add = ();
      my $y = 0;
      my $date = "";
      
      if (defined $dataref->[$i]) {
	while (defined $dataref->[$i]) {
	  #for each data set
	  my $j = 0;
	  
	  while (defined $dataref->[$i]->[0][$j]) {
	    # for each point in the data set...
	    $date = $dataref->[$i]->[0][$j];
	    if (defined $sub{$date}) {
	      $sub{$date} += $dataref->[$i]->[1][$j];
	      $add{$date} += $dataref->[$i]->[2][$j];
	    } else {
	      $sub{$date} = $dataref->[$i]->[1][$j];
	      $add{$date} = $dataref->[$i]->[2][$j];
	    }
	    $j++;
	  };
	  $i++;
	}
	
	# save create data set
	foreach $date (reverse (sort (keys (%sub)))) {
	  push @{$data[0]}, $date;
	  push @{$data[1]}, $sub{$date};
	  push @{$data[2]}, $add{$date};
	};
      }
      return @data;
    };
  
  
  ###############################################################################
  # generate a single data set of total activity per parameter specified 
  #
  sub generate_param_data
    {
      my $dataref = $_[0];
      my $param = $_[1];
      my $i = 0;
      my @data = ();
      my $sub = 0;
      my $add = 0;
      my $y = 0;
      my $date = "";
      
      if (defined $dataref->[$i]) {
	while (defined $dataref->[$i]) {
	  #for each data set
	  my $j = 0;
	  $add = 0;
	  $sub = 0;
	  
	  while (defined $dataref->[$i]->[0][$j]) {
	    # for each point in the data set...
	    $sub += $dataref->[$i]->[1][$j];
	    $add += $dataref->[$i]->[2][$j];
	    $j++;
	  };
	  if (length $param->[$i] <= 20) {
	    push @{$data[0]}, $param->[$i];
	  } else {
	    push @{$data[0]}, ".." . substr ($param->[$i], -20, 20);
	  }
	  push @{$data[1]}, $sub;
	  push @{$data[2]}, $add;
	  $i++;
	}
      }
      
      return @data;
    };
  
  
  ###############################################################################
  # find min max dates for all data + max number of unique dates
  #
  sub find_unique_dates {
    my $dataref = $_[0];
    my %unique_dates = ();
    my $min_date = undef;
    my $max_date = undef;
    my $i = 0;
    my $date = 0;
    
    if (defined $dataref->[$i]) {
      
      while (defined $dataref->[$i]) {
	#for each data set...
	my $j = 0;
	
	while (defined $dataref->[$i]->[0][$j]) {
	  if (length ($dataref->[$i]->[0][$j]) > 2) {
	    # for each point in the data set...
	    $unique_dates{$dataref->[$i]->[0][$j]} = 1;
	    if (defined $min_date) {
	      if ($dataref->[$i]->[0][$j] lt $min_date) {
		$min_date = $dataref->[$i]->[0][$j];
	      } 
	    } else {
	      $min_date = $dataref->[$i]->[0][$j];
	    }
	    if (defined $max_date) {
	      if ($dataref->[$i]->[0][$j] gt $max_date) {
		$max_date = $dataref->[$i]->[0][$j];
	      }
	    } else {
	      $max_date = $dataref->[$i]->[0][$j];
	    }
	  }
	  $j++;
	};
	$i++;
      }
    }
    return ($min_date, $max_date, %unique_dates);
  };


  ###############################################################################
  # calculate sample period (in secs)
  # period is rounded up to an integer number of days 
  # and such that we get no more than $max_num_plot_columns per plot.
  #
  sub calc_sample_period {
    my $min_date = $_[0];
    my $max_date = $_[1];
    my $period = undef;

    if ((defined $min_date) && (defined $max_date)) {
      $period = (str2date($max_date) - str2date($min_date)) / $max_num_plot_columns;

      $period = int(($period + $secs_per_day - 1) / $secs_per_day) * $secs_per_day;

      $period = $min_secs_per_period if ($period < $min_secs_per_period);
    }

    return $period;
  }


  sub generate_date_ordered_log {
    my $date;
    my $out_file = "$_[0]";
    my $i = 0;

    print "  generating '$out_file'\n";
    open LOGFILE,">$out_file"
      or die "Can't create '$out_file' file";

    print_html_header (\*LOGFILE, "Archive Date Log");

    print LOGFILE "<body bgcolor=\"$bgcolor3\">\n";

    print LOGFILE <<EOS;
    <font face="Verdana, Helvetica, Arial">
    <font size=6><b><u><center>Date Ordered Log</center></u></b></font>
    <br>
    <center>
    <table>
      <tr>
EOS

    foreach $date (reverse (sort (keys (%date_log)))) {
      if ((int($i / 2) * 2) == $i) {
	print LOGFILE "<tr valign=\"top\"><td bgcolor=\"$bgcolor2\"><b>$date</b></td>\n";
	print LOGFILE "<td bgcolor=\"$bgcolor2\"><pre>$date_log{$date}</pre></td></tr>";
      } else {
	print LOGFILE "<tr valign=\"top\"><td bgcolor=\"$bgcolor1\"><b>$date</b></td>\n";
	print LOGFILE "<td bgcolor=\"$bgcolor1\"><pre>$date_log{$date}</pre></td></tr>";
      }
      $i++;
    }

    print LOGFILE <<EOS;
    </table>
    </center>
  </font>
EOS

    print_html_footer (\*LOGFILE);

    close LOGFILE;
  }

}
